 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.keys;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collections ;
 import java.util.List ;
 import java.util.StringTokenizer ;

 import org.eclipse.ui.internal.util.Util;

 /**
  * <p>
  * A <code>KeySequence</code> is defined as a list of zero or more
  * <code>KeyStrokes</code>, with the stipulation that all
  * <code>KeyStroke</code> objects must be complete, save for the last one,
  * whose completeness is optional. A <code>KeySequence</code> is said to be
  * complete if all of its <code>KeyStroke</code> objects are complete.
  * </p>
  * <p>
  * All <code>KeySequence</code> objects have a formal string representation
  * available via the <code>toString()</code> method. There are a number of
  * methods to get instances of <code>KeySequence</code> objects, including one
  * which can parse this formal string representation.
  * </p>
  * <p>
  * All <code>KeySequence</code> objects, via the <code>format()</code>
  * method, provide a version of their formal string representation translated by
  * platform and locale, suitable for display to a user.
  * </p>
  * <p>
  * <code>KeySequence</code> objects are immutable. Clients are not permitted
  * to extend this class.
  * </p>
  *
  * @deprecated Please use org.eclipse.jface.bindings.keys.KeySequence
  * @since 3.0
  */
 public final class KeySequence implements Comparable {

     /**
      * The delimiter between multiple key strokes in a single key sequence --
      * expressed in the formal key stroke grammar. This is not to be displayed
      * to the user. It is only intended as an internal representation.
      */
     public final static String KEY_STROKE_DELIMITER = "\u0020"; //$NON-NLS-1$

     /**
      * An empty key sequence instance for use by everyone.
      */
     private final static KeySequence EMPTY_KEY_SEQUENCE = new KeySequence(
             Collections.EMPTY_LIST);

     /**
      * An internal constant used only in this object's hash code algorithm.
      */
     private final static int HASH_FACTOR = 89;

     /**
      * An internal constant used only in this object's hash code algorithm.
      */
     private final static int HASH_INITIAL = KeySequence.class.getName()
             .hashCode();

     /**
      * The set of delimiters for <code>KeyStroke</code> objects allowed
      * during parsing of the formal string representation.
      */
     public final static String KEY_STROKE_DELIMITERS = KEY_STROKE_DELIMITER
             + "\b\r\u007F\u001B\f\n\0\t\u000B"; //$NON-NLS-1$

     /**
      * Gets an instance of <code>KeySequence</code>.
      *
      * @return a key sequence. This key sequence will have no key strokes.
      * Guaranteed not to be <code>null</code>.
      */
     public static KeySequence getInstance() {
         return EMPTY_KEY_SEQUENCE;
     }

     /**
      * Gets an instance of <code>KeySequence</code> given a key sequence and
      * a key stroke.
      *
      * @param keySequence
      * a key sequence. Must not be <code>null</code>.
      * @param keyStroke
      * a key stroke. Must not be <code>null</code>.
      * @return a key sequence that is equal to the given key sequence with the
      * given key stroke appended to the end. Guaranteed not to be
      * <code>null</code>.
      */
     public static KeySequence getInstance(KeySequence keySequence,
             KeyStroke keyStroke) {
         if (keySequence == null || keyStroke == null) {
             throw new NullPointerException ();
         }

         List keyStrokes = new ArrayList (keySequence.getKeyStrokes());
         keyStrokes.add(keyStroke);
         return new KeySequence(keyStrokes);
     }

     /**
      * Gets an instance of <code>KeySequence</code> given a single key
      * stroke.
      *
      * @param keyStroke
      * a single key stroke. Must not be <code>null</code>.
      * @return a key sequence. Guaranteed not to be <code>null</code>.
      */
     public static KeySequence getInstance(KeyStroke keyStroke) {
         return new KeySequence(Collections.singletonList(keyStroke));
     }

     /**
      * Gets an instance of <code>KeySequence</code> given an array of key
      * strokes.
      *
      * @param keyStrokes
      * the array of key strokes. This array may be empty, but it
      * must not be <code>null</code>. This array must not contain
      * <code>null</code> elements.
      * @return a key sequence. Guaranteed not to be <code>null</code>.
      */
     public static KeySequence getInstance(KeyStroke[] keyStrokes) {
         return new KeySequence(Arrays.asList(keyStrokes));
     }

     /**
      * Gets an instance of <code>KeySequence</code> given a list of key
      * strokes.
      *
      * @param keyStrokes
      * the list of key strokes. This list may be empty, but it must
      * not be <code>null</code>. If this list is not empty, it
      * must only contain instances of <code>KeyStroke</code>.
      * @return a key sequence. Guaranteed not to be <code>null</code>.
      */
     public static KeySequence getInstance(List keyStrokes) {
         return new KeySequence(keyStrokes);
     }
     
     /**
      * Gets an instance of <code>KeySequence</code> given a new-style key
      * sequence.
      *
      * @param newKeySequence
      * The new-style key sequence to convert into a legacy key
      * sequence; must not be <code>null</code>.
      * @return a key sequence; never <code>null</code>.
      */
     public static final KeySequence getInstance(
             final org.eclipse.jface.bindings.keys.KeySequence newKeySequence) {
         final org.eclipse.jface.bindings.keys.KeyStroke[] newKeyStrokes = newKeySequence
                 .getKeyStrokes();
         final int newKeyStrokesCount = newKeyStrokes.length;
         final List legacyKeyStrokes = new ArrayList (newKeyStrokesCount);

         for (int i = 0; i < newKeyStrokesCount; i++) {
             final org.eclipse.jface.bindings.keys.KeyStroke newKeyStroke = newKeyStrokes[i];
             legacyKeyStrokes.add(SWTKeySupport
                     .convertAcceleratorToKeyStroke(newKeyStroke
                             .getModifierKeys()
                             | newKeyStroke.getNaturalKey()));
         }
         
         return new KeySequence(legacyKeyStrokes);
     }

     /**
      * Gets an instance of <code>KeySequence</code> by parsing a given a
      * formal string representation.
      *
      * @param string
      * the formal string representation to parse.
      * @return a key sequence. Guaranteed not to be <code>null</code>.
      * @throws ParseException
      * if the given formal string representation could not be
      * parsed to a valid key sequence.
      */
     public static KeySequence getInstance(String string) throws ParseException {
         if (string == null) {
             throw new NullPointerException ();
         }

         List keyStrokes = new ArrayList ();
         StringTokenizer stringTokenizer = new StringTokenizer (string,
                 KEY_STROKE_DELIMITERS);

         while (stringTokenizer.hasMoreTokens()) {
             keyStrokes.add(KeyStroke.getInstance(stringTokenizer.nextToken()));
         }

         try {
             return new KeySequence(keyStrokes);
         } catch (Throwable t) {
             throw new ParseException(
                     "Could not construct key sequence with these key strokes: " //$NON-NLS-1$
 + keyStrokes);
         }
     }

     /**
      * The cached hash code for this object. Because <code>KeySequence</code>
      * objects are immutable, their hash codes need only to be computed once.
      * After the first call to <code>hashCode()</code>, the computed value
      * is cached here for all subsequent calls.
      */
     private transient int hashCode;

     /**
      * A flag to determine if the <code>hashCode</code> field has already
      * been computed.
      */
     private transient boolean hashCodeComputed;

     /**
      * The list of key strokes for this key sequence.
      */
     private List keyStrokes;

     /**
      * Constructs an instance of <code>KeySequence</code> given a list of key
      * strokes.
      *
      * @param keyStrokes
      * the list of key strokes. This list may be empty, but it must
      * not be <code>null</code>. If this list is not empty, it
      * must only contain instances of <code>KeyStroke</code>.
      */
     private KeySequence(List keyStrokes) {
         this.keyStrokes = Util.safeCopy(keyStrokes, KeyStroke.class);

         for (int i = 0; i < this.keyStrokes.size() - 1; i++) {
             KeyStroke keyStroke = (KeyStroke) this.keyStrokes.get(i);

             if (!keyStroke.isComplete()) {
                 throw new IllegalArgumentException ();
             }
         }
     }

     /**
      * @see java.lang.Object#equals(java.lang.Object)
      */
     public int compareTo(Object object) {
         KeySequence castedObject = (KeySequence) object;
         int compareTo = Util.compare(keyStrokes, castedObject.keyStrokes);
         return compareTo;
     }

     /**
      * Returns whether or not this key sequence ends with the given key
      * sequence.
      *
      * @param keySequence
      * a key sequence. Must not be <code>null</code>.
      * @param equals
      * whether or not an identical key sequence should be considered
      * as a possible match.
      * @return <code>true</code>, iff the given key sequence ends with this
      * key sequence.
      */
     public boolean endsWith(KeySequence keySequence, boolean equals) {
         if (keySequence == null) {
             throw new NullPointerException ();
         }

         return Util.endsWith(keyStrokes, keySequence.keyStrokes, equals);
     }

     /**
      * @see java.lang.Object#equals(java.lang.Object)
      */
     public boolean equals(Object object) {
         if (!(object instanceof KeySequence)) {
             return false;
         }

         return keyStrokes.equals(((KeySequence) object).keyStrokes);
     }

     /**
      * Formats this key sequence into the current default look.
      *
      * @return A string representation for this key sequence using the default
      * look; never <code>null</code>.
      */
     public String format() {
         return KeyFormatterFactory.getDefault().format(this);
     }

     /**
      * Returns the list of key strokes for this key sequence.
      *
      * @return the list of key strokes keys. This list may be empty, but is
      * guaranteed not to be <code>null</code>. If this list is not
      * empty, it is guaranteed to only contain instances of <code>KeyStroke</code>.
      */
     public List getKeyStrokes() {
         return keyStrokes;
     }

     /**
      * @see java.lang.Object#hashCode()
      */
     public int hashCode() {
         if (!hashCodeComputed) {
             hashCode = HASH_INITIAL;
             hashCode = hashCode * HASH_FACTOR + keyStrokes.hashCode();
             hashCodeComputed = true;
         }

         return hashCode;
     }

     /**
      * Returns whether or not this key sequence is complete. Key sequences are
      * complete iff all of their key strokes are complete.
      *
      * @return <code>true</code>, iff the key sequence is complete.
      */
     public boolean isComplete() {
         return keyStrokes.isEmpty()
                 || ((KeyStroke) keyStrokes.get(keyStrokes.size() - 1))
                         .isComplete();
     }

     /**
      * Returns whether or not this key sequence is empty. Key sequences are
      * complete iff they have no key strokes.
      *
      * @return <code>true</code>, iff the key sequence is empty.
      */
     public boolean isEmpty() {
         return keyStrokes.isEmpty();
     }

     /**
      * Returns whether or not this key sequence starts with the given key
      * sequence.
      *
      * @param keySequence
      * a key sequence. Must not be <code>null</code>.
      * @param equals
      * whether or not an identical key sequence should be considered
      * as a possible match.
      * @return <code>true</code>, iff the given key sequence starts with
      * this key sequence.
      */
     public boolean startsWith(KeySequence keySequence, boolean equals) {
         if (keySequence == null) {
             throw new NullPointerException ();
         }

         return Util.startsWith(keyStrokes, keySequence.keyStrokes, equals);
     }

     /**
      * Returns the formal string representation for this key sequence.
      *
      * @return The formal string representation for this key sequence.
      * Guaranteed not to be <code>null</code>.
      * @see java.lang.Object#toString()
      */
     public String toString() {
         return KeyFormatterFactory.getFormalKeyFormatter().format(this);
     }
 }

